<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>3.3.1 组件的本质</title>
</head>
<body>
    
</body>
    <script>
        /**
         * 组件就是一组 DOM 元素的封装
         * - 这组 DOM 元素就是组件要渲染的内容，
         *    因此我们可以定义一个函数来代表组件，
         *    而函数的返回值就代表组件要渲染的内容
         *
        */
        const MyComponent = function () { // 新增
            return {
                tag: 'div',
                props: {
                    onClick: () => alert('hello')
                },
                children: 'MyComponent click me'
            }
        }

        // MyComponent1 是一个对象
        const MyComponent1 = { // 新增
            render() {
                return {
                    tag: 'div',
                    props: {
                        onClick: () => alert('hello 1')
                    },
                    children: 'MyComponent1 click me'
                }
            }
        }
        

        // 虚拟 DOM
        const vnode = {
            // 标签名称
            tag: MyComponent1, // 新增
        }

        /**
         * 渲染器
         * @param {Object} vnode 虚拟 DOM
         * @param {HTMLElement} container 挂载点
         * */ 
        function renderer(vnode, container){
            if (typeof vnode.tag === 'string') { // 新增 判断
                // 说明 vnode 描述的是标签元素
                mountElement(vnode, container)
            } else if (typeof vnode.tag === 'function') {
                // 说明 vnode 描述的是组件
                mountComponent(vnode, container)
            } else if (typeof vnode.tag === 'object') {
                // 说明 vnode 描述的是组件
                mountComponent(vnode, container)
            }
        }

        /**
         * mountElement
         * @param {Object} vnode 虚拟 DOM
         * @param {HTMLElement} container 挂载点
         * */ 
        function mountElement(vnode, container) { // 新增 mountElement
            // 使用 vnode.tag 作为标签名称创建 DOM 元素
            const el = document.createElement(vnode.tag)
            // 遍历 vnode.props，将属性、事件添加到 DOM 元素
            for (const key in vnode.props) {
                if (/^on/.test(key)) {
                    // 如果 key 以字符串 on 开头，说明它是事件
                    el.addEventListener(
                        key.substr(2).toLowerCase(), // 事件名称 onClick ---> click
                        vnode.props[key] // 事件处理函数
                    )
                }
            }

            // 处理 children
            if (typeof vnode.children === 'string') {
                // 如果 children 是字符串，说明它是元素的文本子节点
                el.appendChild(document.createTextNode(vnode.children))
            } else if (Array.isArray(vnode.children)) {
                // 递归地调用 renderer 函数渲染子节点，使用当前元素 el 作为挂载点
                vnode.children.forEach(child => renderer(child, el))
            }

            // 将元素添加到挂载点下
            container.appendChild(el)
        }

        /**
         * mountComponent
         * @param {Object} vnode 虚拟 DOM
         * @param {HTMLElement} container 挂载点
         * */
        function mountComponent(vnode, container) { // 新增 mountComponent
            // 调用组件函数，获取组件要渲染的内容（虚拟 DOM）
            let subtree
            if (typeof vnode.tag === 'object') {
                subtree = vnode.tag.render()
            } else {
                subtree = vnode.tag()
            }
            // 递归地调用 renderer 渲染 subtree
            renderer(subtree, container)
        }

        renderer(vnode, document.body) // body 作为挂载点

        
    </script>
</html>